#include "processthread.h"

//Libraries
#include "logger.h"
#include "config.h"
#include "loggedusers.h"

//QT
#include <QtNetwork>
#include <QtSql>

ProcessThread::ProcessThread(int socketDescriptor, LoggedUsers *loggedUsers, QObject *parent)
    : QThread(parent), userId(0), socketDescriptor(socketDescriptor), loggedUsers(loggedUsers)
{
}


/**
  * Setup new thread
  */
void ProcessThread::run()
{
    QTimer::singleShot(0, this, SLOT(setup()));
    exec();
}
void ProcessThread::setup()
{
    socket = new QSslSocket(this);


    connect(socket,SIGNAL(readyRead()),this,SLOT(OnReadyRead()));
    connect(socket,SIGNAL(disconnected()),this,SLOT(OnDisconnected()));

    if (!socket->setSocketDescriptor(socketDescriptor)) {
        emit error(socket->error());
        return;
    }

    socket->setLocalCertificate("cert.pem");
    socket->setPrivateKey("key.pem");
    socket->startServerEncryption();

}

/**
  * New data arrived, process it.
  */
void ProcessThread::OnReadyRead()
{
    //Get logger
    Logger *logger = Logger::instance();
    //Get config
    Config *config = Config::instance();

    //Get raw data
    QByteArray inputData = socket->readAll();
    //Split data into request
    QList<QByteArray> request = inputData.split(0x1D);
    //Switch request
    QString command = request.takeFirst();

    if (command == "LOGIN") {
        //Get login and passwd
        if (request.size() < 2) {
            //Two parameters are needed
            this->socketWrite("WRONG_IDENTITY\x1D");
            //Log activity
            logger->log("Bad parameters for Login command received.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        QString loginName = request.takeFirst();
        QString loginPass = request.takeFirst();

        //Prepare query
        QSqlQuery query;
        query.prepare("select rowid from users where name = :name and pass = :pass");
        query.bindValue(":name", loginName);
        query.bindValue(":pass", loginPass);
        query.exec();
        //Get result
        if (query.next()) {
            //User has account, add ID and send logged message.
            QString answer;
            userId = query.value(0).toInt();
            QTextStream(&answer) << "LOGGED\x1D" << userId << "\x1D";
            this->socketWrite(answer.toUtf8());
            //Log to log
            answer.clear();
            QTextStream(&answer) << "Logged user " << loginName << "(" << userId << ") " << (socket->isEncrypted() ? "ENCRYPTED" : "NON-ENCRYPTED");
            logger->log(answer, Logger::NOTICE);
            //Update status of user
            query.prepare("update users set status = 4 where rowid = :userid");
            query.bindValue(":userid",userId);
            if (!query.exec() || query.numRowsAffected() < 1) {
                logger->log("Cannot update user status while logging, please check DB integrity.",Logger::WARNING);
            }
            //Add this thread to users threads
            loggedUsers->addUserThread(userId,this);
            return;
        } else {
            //User is not registered, send failed
            this->socketWrite("WRONG_IDENTITY\x1D");
            //Log to log
            QString answer;
            QTextStream(&answer) << "Wrong identity provided for user " << loginName;
            logger->log(answer,Logger::NOTICE);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
        }
    }

    if (command == "REGISTER") {
        if (userId != 0) {
            //Logged, display error and exit
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log accident
            QString answer;
            QTextStream(&answer) << "Register command from logged user: " << userId;
            logger->log(answer,Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        //Get login and passwd
        if (request.size() < 2 || config->getValue("allow-registration") != "true") {
            //Two parameters are needed or registration is forbidden
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Bad Register command received.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        QString registerName = request.takeFirst();
        QString registerPass = request.takeFirst();

        //Prepare query
        QSqlQuery query;
        query.prepare("select rowid from users where name = :name");
        query.bindValue(":name", registerName);
        query.exec();
        //Get result
        if (query.next()) {
            //User exists, registration failed
            this->socketWrite("IDENTITY_EXISTS\x1D");
            //Log this
            QString answer;
            QTextStream(&answer) << "Identity exist in registration for user: " << registerName;
            logger->log(answer,Logger::NOTICE);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
        } else {
            query.prepare("insert into users (name, pass, status) values (:name, :pass, 0)");
            query.bindValue(":name", registerName);
            query.bindValue(":pass", registerPass);
            if (query.exec()) {
                //Okey, registration done
                this->socketWrite("OK\x1D");
                //Log this
                QString answer;
                QTextStream(&answer) << "User registered: " << registerName;
                logger->log(answer,Logger::NOTICE);
            } else {
                //Something went wrong, so lets send registration failed
                this->socketWrite("REGISTER_FORBIDDEN\x1D");
                //Report it to log
                logger->log("Cannot add user to DB, please check DB integrity.",Logger::WARNING);
                //Terminate connection
                QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            }
        }
    }

    if (command == "LOGOUT") {
        if (userId == 0) {
            //Not logged, display error and exit
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Logout command from non-logged user.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        //Ok, logout user and terminate thread
        QSqlQuery query;
        query.prepare("update users set status = 0 where rowid = :userid");
        query.bindValue(":userid",userId);
        if (query.exec() && query.numRowsAffected() > 0) {
            //OK, logged off
            this->socketWrite("OK\x1D");
            //Log this
            QString answer;
            QTextStream(&answer) << "User logged off, id: " << userId;
            logger->log(answer,Logger::NOTICE);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
        } else {
            //Something gone wrong
            this->socketWrite("SERVER_ERROR\x1D");
            //log it
            logger->log("Cannot logoff user, please check DB integrity.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(500, this, SLOT(OnDisconnected()));
        }
    }

    if (command == "STATUS") {
        if (userId == 0) {
            //Not logged, display error and exit
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Status command from non-logged user.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        //Get status
        if (request.size() < 1) {
            //Parameter is needed
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Bad change status parameters.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        QString status = request.takeFirst();

        QSqlQuery query;
        query.prepare("update users set status = :status where rowid = :userid");
        query.bindValue(":status",status.toInt());
        query.bindValue(":userid",userId);
        if (query.exec() && query.numRowsAffected() > 0) {
            //OK, updated
            this->socketWrite("OK\x1D");
            //Log this
            QString answer;
            QTextStream(&answer) << "User id " << userId << " changed status to " << status.toInt();
            logger->log(answer,Logger::DEBUG);
        } else {
            //Something gone wrong
            this->socketWrite("SERVER_ERROR\x1D");
            //log it
            logger->log("Cannot change user status, please check DB integrity.",Logger::WARNING);
        }
    }

    if (command == "LIST") {
        if (userId == 0) {
            //Not logged, display error and exit
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("List command from non-logged user.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        //Get all online users
        QSqlQuery query;
        query.prepare("select rowid, name, status from users where rowid != :userid order by name desc");
        query.bindValue(":userid",userId);
        query.exec();
        //Get result
        QString answer;
        QTextStream(&answer) << "LISTING\x1D";
        while (query.next()) {
            int q_user_id = query.value(0).toInt();
            QString q_user_name = query.value(1).toString();
            int q_user_status = query.value(2).toInt();
            QTextStream(&answer) << q_user_id << "\x1D" << q_user_name << "\x1D" << q_user_status << "\x1D";
        }
        this->socketWrite(answer.toUtf8());
        //Log this
        answer.clear();
        QTextStream(&answer) << "Listing online users for user id " << userId;
        logger->log(answer,Logger::DEBUG);
    }

    if (command == "PING") {
        //Ping pong command, just answer it :)
        this->socketWrite("PONG\x1D");
        //Log this
        QString answer;
        QTextStream(&answer) << "Ping pong game played for user id " << userId;
        logger->log(answer,Logger::DEBUG);
    }

    if (command == "SEND") {
        if (userId == 0) {
            //Not logged, display error and exit
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Send command from non-logged user.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        //Get login and passwd
        if (request.size() < 2) {
            //Two parameters are needed
            this->socketWrite("WRONG_COMMAND\x1D");
            //Log activity
            logger->log("Bad parameters for Send command.",Logger::WARNING);
            //Terminate connection
            QTimer::singleShot(0, this, SLOT(OnDisconnected()));
            return;
        }
        QString toUserId = request.takeFirst();
        QString messageText = request.takeFirst();

        QThread *usersThread = loggedUsers->getUserThread(toUserId.toInt());
        if (usersThread != 0 && usersThread->isRunning()) {
            connect(this,SIGNAL(dummySendMsgSignal(int,QString)),usersThread,SLOT(sendMsg(int,QString)));
            emit dummySendMsgSignal(userId,messageText);
            disconnect(usersThread,SLOT(sendMsg(int,QString)));
            this->socketWrite("OK\x1D");
            //Log this
            QString answer;
            QTextStream(&answer) << "Message from user id " << userId << " to user id " << toUserId << " sent.";
            logger->log(answer,Logger::DEBUG);
        } else {
            //Log this
            QString answer;
            QTextStream(&answer) << "Message from user id " << userId << " to user id " << toUserId << " NOT sent.";
            logger->log(answer,Logger::DEBUG);
            this->socketWrite("NOT_SENT\x1D");
        }
    }
}

void ProcessThread::sendMsg(int fromUserId, QString message) {
    QString answer;
    QTextStream(&answer) << "RECIVE\x1D" << fromUserId << "\x1D" << message << "\x1D";
    this->socketWrite(answer.toUtf8());
    answer.clear();
    //Get logger
    Logger *logger = Logger::instance();
    QTextStream(&answer) << "Message from user id " << fromUserId << " to user id " << userId << " delivered.";
    logger->log(answer,Logger::DEBUG);
}

/**
  * Client is disconnected, quit thread.
  */
void ProcessThread::OnDisconnected() {
    if (userId != 0) {
        //Delete user from connected threads
        loggedUsers->delUserThread(userId);
        //Set user offline in DB
        QSqlQuery query;
        query.prepare("update users set status = 0 where rowid = :userid");
        query.bindValue(":userid",userId);
        if (!query.exec() || query.numRowsAffected() < 1) {
            //Get logger
            Logger *logger = Logger::instance();
            logger->log("Cannot update user status while logging off, please check DB integrity.",Logger::WARNING);
        }
        //Log activity
        QString answer;
        //Get logger
        Logger *logger = Logger::instance();
        QTextStream(&answer) << "User id " << userId << " disconnected.";
        logger->log(answer,Logger::DEBUG);
    }
    //Disconnect socket
    socket->disconnectFromHost();
    //Terminate this thread
    QTimer::singleShot(0, this, SLOT(terminate()));
}


void ProcessThread::socketWrite(QByteArray data)
{
        // write data
        socket->write(data);
        // send it over network
        socket->flush();
}
